﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;

namespace Songhay.Wpf.UriTree.Models
{
    using System.Collections.Generic;
    using Songhay.Wpf.UriTree.Properties;
    using Songhay.Xml;

    /// <summary>
    /// Defines an OPML set of data.
    /// </summary>
    public class OpmlSet : IOpmlSet
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="OpmlSet"/> class.
        /// </summary>
        /// <param name="pathToSet">The path to set.</param>
        public OpmlSet(string pathToSet)
        {
            this._opmlWorker = new BackgroundWorker();
            this._opmlWorker.DoWork += new DoWorkEventHandler(_opmlWorker_DoWork);
            this._opmlWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_opmlWorker_RunWorkerCompleted);

            this._fileIsLoaded = false;
            this._fileLoadAttempted = false;
            this._filePathEmpty = string.IsNullOrEmpty(pathToSet);

            if(!this._filePathEmpty) this._opmlWorker.RunWorkerAsync(pathToSet);
            else this._fileLoadAttempted = true;
        }

        /// <summary>
        /// Gets the error message.
        /// </summary>
        /// <value>The error message.</value>
        public string ErrorMessage { get { return this._errorMessage; } }

        /// <summary>
        /// Gets a value indicating whether the underlying OPML file is loaded.
        /// </summary>
        /// <value><c>true</c> if the underlying OPML file is loaded; otherwise, <c>false</c>.</value>
        public bool FileIsLoaded { get { return this._fileIsLoaded; } }

        /// <summary>
        /// Gets a value indicating whether an attempt to load the underlying OPML file was made.
        /// </summary>
        /// <value><c>true</c> if an attempt to load the underlying OPML file was made; otherwise, <c>false</c>.</value>
        public bool FileLoadAttempted { get { return this._fileLoadAttempted; } }

        /// <summary>
        /// Gets a value indicating whether the path to the underlying OPML file is empty.
        /// </summary>
        /// <value><c>true</c> if the path to the underlying OPML file is empty; otherwise, <c>false</c>.</value>
        public  bool FilePathEmpty { get { return this._filePathEmpty; } }

        /// <summary>
        /// Occurs when the underlying OPML file is loaded.
        /// </summary>
        public event EventHandler<EventArgs> OpmlFileLoaded;

        /// <summary>
        /// Gets the opml categories.
        /// </summary>
        /// <returns></returns>
        public  IEnumerable<XElement> GetOpmlCategories()
        {
            var nodes = XObjectUtility.GetXNodes(this._opmlRootNode, Settings.Default.XpathUriCategories);
            return nodes.Cast<XElement>();
        }

        /// <summary>
        /// Gets the opml categories.
        /// </summary>
        /// <param name="currentElement">The current node.</param>
        /// <returns></returns>
        public static IEnumerable<XElement> GetOpmlCategories(XElement currentElement)
        {
            var nodes = XObjectUtility.GetXNodes(currentElement, Settings.Default.XpathUriCategories);
            return nodes.Cast<XElement>();
        }

        /// <summary>
        /// Gets the opml resources.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<XElement> GetOpmlResources()
        {
            var nodes = XObjectUtility.GetXNodes(this._opmlRootNode, Settings.Default.XpathUriResources);
            return nodes.Cast<XElement>();
        }

        /// <summary>
        /// Gets the opml resources.
        /// </summary>
        /// <param name="currentElement">The current node.</param>
        /// <returns></returns>
        public static IEnumerable<XElement> GetOpmlResources(XElement currentElement)
        {
            var nodes = XObjectUtility.GetXNodes(currentElement, Settings.Default.XpathUriResources);
            return nodes.Cast<XElement>();
        }

        /// <summary>
        /// Gets the opml URI value.
        /// </summary>
        /// <param name="currentElement">The current node.</param>
        /// <returns></returns>
        public static string GetOpmlUriValue(XElement currentElement)
        {
            return (string)XObjectUtility.GetXAttributeValue(currentElement, Settings.Default.XpathUriValue, true);
        }

        /// <summary>
        /// Gets the display name of the resource.
        /// </summary>
        /// <param name="currentElement">The current node.</param>
        /// <returns></returns>
        public static string GetResourceDisplayName(XElement currentElement)
        {
            return OpmlSet.FormatDisplayName((string)XObjectUtility.GetXAttributeValue(currentElement, Settings.Default.XpathUriName, true));
        }

        /// <summary>
        /// Determines whether the test value is like a URI.
        /// </summary>
        /// <param name="test">The test.</param>
        /// <returns>
        ///    <c>true</c> if the test value is like a URI; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsAnyUri(string test)
        {
            if(string.IsNullOrEmpty(test)) return false;

            bool bln = false;

            //TODO: Change this to a Regular Expression:
            if(test.IndexOf(@".htm") != -1) return true;
            if(test.IndexOf(@":\") != -1) return false;
            if(test.IndexOf("://") != -1) bln = true;

            return bln;
        }

        void _opmlWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            string pathToSet = (string)e.Argument;
            try
            {
                this._opmlSet = XDocument.Load(pathToSet);
                var s = this._opmlSet.ToString();
                s = XmlUtility.StripNamespaces(s);
                this._opmlSet = XDocument.Parse(s);
                this._fileIsLoaded = true;
            }
            catch(Exception ex)
            {
                this._errorMessage = ex.Message;
                this._errorMessage = String.Format("There is a problem with the XML file, “{0}.”{1}({2})",
                    pathToSet, Environment.NewLine, this._errorMessage);
            }
        }

        void _opmlWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this._fileLoadAttempted = true;

            if(this._fileIsLoaded)
            {
                this._opmlRootNode = this._opmlSet.Root.XPathSelectElement(Settings.Default.XpathOpmlBody);
            }

            if(OpmlFileLoaded != null) this.OpmlFileLoaded(this, new EventArgs());
        }

        static string FormatDisplayName(string DisplayName)
        {
            if(!string.IsNullOrEmpty(DisplayName))
            {
                DisplayName = DisplayName.Replace("&", "&&");
            }

            return DisplayName;
        }

        string _errorMessage;

        bool _fileIsLoaded;
        bool _fileLoadAttempted;
        bool _filePathEmpty;

        BackgroundWorker _opmlWorker;
        XDocument _opmlSet;
        XElement _opmlRootNode;
    }
}
